package com.liu.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;

import com.liu.configuration.CustomObjectMapper;
import com.liu.constant.Constants;
import com.liu.core.RedisTemplate;
import com.liu.entity.LiuLoginUser;
import com.liu.entity.LiuMenu;
import com.liu.entity.LiuRole;
import com.liu.entity.LiuUser;
import com.liu.dao.LiuUserDao;
import com.liu.exception.PasswordIncorrectException;
import com.liu.exception.UserNotFoundException;
import com.liu.service.LiuUserService;



import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;


import javax.annotation.Resource;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户信息表(LiuUser)表服务实现类
 *
 * @author makejava
 * @since 2023-01-13 20:34:59
 */
@Service("liuUserService")
@Slf4j
public class LiuUserServiceImpl implements LiuUserService {
    @Resource
    private LiuUserDao liuUserDao;

    @Resource
    private RestTemplate restTemplate;

    //用于序列化和反序列化的
    @Resource
    private CustomObjectMapper objectMapper;

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 通过ID查询单条数据
     *
     * @param userId 主键
     * @return 实例对象
     */
    @Override
    public LiuUser queryById(Long userId) {
        return this.liuUserDao.queryById(userId);
    }

    /**
     * 分页查询
     *
     * @param liuUser     筛选条件
     * @param pageRequest 分页对象
     * @return 查询结果
     */
    @Override
    public Page<LiuUser> queryByPage(LiuUser liuUser, PageRequest pageRequest) {
        long total = this.liuUserDao.count(liuUser);
        return new PageImpl<>(this.liuUserDao.queryAllByLimit(liuUser, pageRequest), pageRequest, total);
    }

    /**
     * 新增数据
     *
     * @param liuUser 实例对象
     * @return 实例对象
     */
    @Override
    public LiuUser insert(LiuUser liuUser) {
        //这里密码应当加密，存明文是不合适的
        this.liuUserDao.insert(liuUser);
        return liuUser;
    }

    /**
     * 修改数据
     *
     * @param liuUser 实例对象
     * @return 实例对象
     */
    @Override
    public LiuUser update(LiuUser liuUser) {
        this.liuUserDao.update(liuUser);
        return this.queryById(liuUser.getUserId());
    }

    /**
     * 通过主键删除数据
     *
     * @param userId 主键
     * @return 是否成功
     */
    @Override
    public boolean deleteById(Long userId) {
        return this.liuUserDao.deleteById(userId) > 0;
    }

    @Override
    public LiuLoginUser login(String username, String password) throws JsonProcessingException {
        //1.登录，使用用户名查询，可能结果如下：1、没有该账户 ；
        LiuUser liuUser = liuUserDao.queryByName(username);
        if (liuUser == null) {
            log.info("执行登录操作：【" + username + "】，该用户用户名输入错误");
            throw new UserNotFoundException("执行登录操作：【" + username + "】，该用户用户名输入错误");
        }
        // 2、查到了，比较密码不正确失败；
        if (!password.equals(liuUser.getPassword())) {
            //这里不是说出现了异常，所以使用info来记录
            log.info("执行登录操作：【" + username + "】，该用户密码输入错误");
            throw new PasswordIncorrectException("执行登录操作：【" + username + "】，该用户密码输入错误");
        }
        // 3、验证成功

        //(1)生成token
        //这是最暴力的一种封装
        String token = UUID.randomUUID().toString();

        //在service获取ip,先获取request
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        UserAgent userAgent = new UserAgent(request.getHeader("User-Agent"));


        // 通过ip获取其所属的地址
        ResponseEntity<String> result = restTemplate.getForEntity("https://whois.pconline.com.cn/ipJson.jsp?ip="+request.getRemoteHost()+"&json=true", String.class);
        String body = result.getBody();
        //反序列化
        Map<String,String> map = objectMapper.readValue(body, new TypeReference<Map>() {
        });

        String location = map.get("addr")+map.get("pro")+map.get("city")+map.get("region");
        System.out.println(location);

        //(2)封装一个LiuLoginUser,保存到redis
//        LiuLoginUser liuLoginUser=new LiuLoginUser();
        //一个个导入过于麻烦，这里已经有了@Builder标签，因此可以使用建造者模式构建
        LiuLoginUser liuLoginUser = LiuLoginUser.builder()
                .userId(liuUser.getUserId())
                .token(token)
                .ipaddr(request.getRemoteAddr())
                .loginTime(new Date())
                .os(userAgent.getOperatingSystem().getName())
                .browser(userAgent.getBrowser().getName())
                .loginLocation(location)
                .liuUser(liuUser)
                .build();

        //key进行处理 token:username:uuid

        //1、根据用户名生成一个key前缀token:username:
        String keyPrefix = Constants.TOKEN_PREFIX+username+":";
        //查询token：username:前缀的数据
        Set<String> keys = redisTemplate.keys(keyPrefix + "*");

        //删除原来的数据
        keys.forEach(key -> redisTemplate.remove(key));
        //把新的数据加入redis

        redisTemplate.setObject(keyPrefix+token,liuLoginUser,Constants.TOKEN_TIME);
        return liuLoginUser;
    }

    @Override
    public void logout() {
        //删除redis中的token和redis数据/,这里需要redis的key，key的组成是request里面head的一个我们定义的由uuid组成的token
        //因此获取request，并获取token
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String token = request.getHeader(Constants.TOKEN_AUTHORIZATION);
        //这是前端传递username方法,后端rediskeys方法的话去看template写法，后期我感觉要改
        String username = request.getHeader("username");
        redisTemplate.remove(Constants.TOKEN_PREFIX+username+":"+token);
    }

    @Override
    public HashMap<String,List<String>> getInfo(){
        //获取当前登录用户
        LiuLoginUser loginUser = getLoginUser();

        //查询当前用户的角色和权限
        LiuUser liuUser = liuUserDao.getInfo(loginUser.getUserId());

        //处理权限和角色的相关信息
        //存储模板如下
        // (1) roles:token :[admin,xxx,yyy]  perms:token:[system:user:add,system:user:update]
        List<String> roleTags = liuUser.getLiuRoles().stream().map(LiuRole::getRoleTag).collect(Collectors.toList());

        redisTemplate.setObject(Constants.ROLE_PREFIX+loginUser.getToken(),roleTags,Constants.TOKEN_TIME);

        List<String> prems =new ArrayList<>();
        //[{roleName:cc,roleTag:xxx,perms:[{id,'xxx',perm:'system'},{id,'xxx',perms:'system}]},[]]
        //变成下列情况
        //['system','system:user:add']
        liuUser.getLiuRoles().stream().map(LiuRole::getLiuMenus).forEach(menus -> {
            prems.addAll(menus.stream().map(LiuMenu::getPerms).collect(Collectors.toList()));
            System.out.println(prems);
        });
        redisTemplate.setObject(Constants.PERM_PREFIX+loginUser.getToken(),prems,Constants.TOKEN_TIME);

        //整合数据
        HashMap<String,List<String>> data =new HashMap<>();
        data.put("roles",roleTags);
        data.put("perms",prems);

        return data;

    }

    /**
     * 作用是通过redis获取当前登录用户信息
     * @return
     */
    private LiuLoginUser getLoginUser(){
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String token = request.getHeader(Constants.TOKEN_AUTHORIZATION);
        if (token == null) {
            throw new RuntimeException("这里是server的登录出现了异常，获取token失败");
        }
        Set<String> keys = redisTemplate.keys(Constants.TOKEN_PREFIX + "*" + token);
        if (keys == null || keys.size() == 0) {
            throw new RuntimeException("这里是server的登录出现了异常，获取key失败");
        }
        String tokenKey = (String) keys.toArray()[0];

        //3.使用token取redis中查看，有没有对应的loginUser
        return redisTemplate.getObject(tokenKey, new TypeReference<LiuLoginUser>() {

        });
    }

    public static void main(String[] args) {
        //使用java发送http请求
        RestTemplate restTemplate =new RestTemplate();
        ResponseEntity<String> forEntity = restTemplate.getForEntity("https://whois.pconline.com.cn/ipJson.jsp?ip=127.78.87", String.class);
        System.out.println(forEntity);
    }
}
